图片加载
上级博客的三级缓存只针对了网络图片加载的情况,如果我们要加载其他来源的图片该如何呢?比如本地图片(file://)、或者程序本身的图片(drawable://),又或者用户需要读取指定外存的图片时该如何?所以我们加载图片的代码不能是硬编码,需要为后期扩展提供功能。要保证扩展性,就必须抽象。
ILoader 只定义了一个接口,只用一个加载图片的方法。
我们再来看看现有的 Loader 的实现代码:
可以见得加载图片的过程如下:
- 判断缓存中是否含有该图片;
- 如果有则将图片直接投递到UI线程,并且更新UI;
- 如果没有缓存,则从对应的地方获取到图片,并且将图片缓存起来,然后再将结果投递给UI线程,更新UI;
从本地、程序资源、assets 中加载图片的过程是怎样的呢?跟加载网络图片的过程有何区别。我们可以发现,不管从哪里加载图片,这些逻辑都是通用的,因此可以抽象一个 AbsLoader 类。它将这几个过程抽象起来,只将变化的部分交给子类处理,就相当于 AbsLoader 封装了一个逻辑框架( 模板方法模式),大致代码如下 :
代码逻辑如上所述实现了一个模板函数,变化的部分就是 onLoadImage,子类在这里实现真正的加载图片的方法。比如从网络上加载图片。
按照上述代码,我们再一个本地图片加载实现类(LocalLoader)。
我们的程序该根据什么条件来选择加载器呢?我们加载网络图片的 uri 格式统一为 http://xxx/image.jpg,或者 https 打头;而本地图片的 uri 为 file://sdcard/xxx/image.jpg。也就是说 uri 格式为 schema:// + 图片路径,而 schema 的值可能是:HTTP(“http”), HTTPS(“https”), FILE(“file”), CONTENT(“content”), ASSETS(“assets”), DRAWABLE(“drawable”), UNKNOWN(“”)。现在我们可以创建一个枚举类 Scheme,负责声明各个常量值以及解析图片 uri。
如果你要实现自己的 Loader 来加载特定的格式,那么它的 uri 格式必须以 schema:// 开头,否则解析会错误,例如可以为 drawable://image,然后你注册一个 schema 为 “drawable” 的Loader到 LoaderManager 中,ImageLoader 在加载图片时就会使用你注册的 Loader 来加载图片,这样就可以应对用户的多种多样的需求。如果不能拥抱变化那就不能称之为框架了,应该叫功能模块。
其中 LoaderManager 代码如下。
LoaderManager 负责管理 ILoader 的实现类,默认注册了 HTTP、HTTPS、FILE 三种 Scheme 及对应的实现加载类。如果用户自定义图片加载类,那么需要将它注册到 LoaderManager 中。注册方式为:
然后修改 RequestDispatcher 中相应的代码:
图片解码
我们定义的 UrlLoader 和 LocalLoader 都使用 BitmapFactory 默认的解码方式来获取位图(Bitmap)。如果用来展示图片的 ImageView 的宽高是位图的宽高的几分之一,那么直接加载原图而不进行缩放,明显浪费内存,甚至可能出现加载一张大图而导致 OOM 的极端情况。那么我们必须提供相关 API 以便用户选择某种方式对图片进行缩放。解析图片的一般过程如下:
- 创建 BitmapFactory.Options options,设置 options.inJustDecodeBounds = true,使得只解析图片尺寸等信息;
- 根据 ImageView 的尺寸来检查是否需要缩小要加载的图片以及计算缩放比例;
- 设置 options.inJustDecodeBounds = false,然后按照 options 设置的缩小比例来加载图片。
我们来创建一个类:BitmapDecoder,它使用 decodeBitmap 方法封装上述过程 ( 模板方法模式 ),用户只需要实现一个子类,并且覆写 BitmapDecoder 的 decodeBitmapWithOption 实现图片加载即可完成这个过程。代码如下:
在 decodeBitmap 中,我们首先创建 BitmapFactory.Options 对象,并且设置 options.inJustDecodeBounds = true,然后第一次调用 decodeBitmapWithOption(options),使得只解析图片尺寸等信息;然后调用 calculateInSmall 方法,该方法会调用 computeInSmallSize 来根据 ImageView 的尺寸来检查是否需要缩小要加载的图片以及计算缩放比例,在 calculateInSmall 方法的最后将
options.inJustDecodeBounds = false,使得下次再次 decodeBitmapWithOption(options) 时会加载图片;那最后一步必然就是调用 decodeBitmapWithOption(options),这样图片就会按照按照 options 设置的缩小比例来加载图片。
我们使用这个辅助类封装了这个麻烦、重复的过程,在一定程度上简化了代码,也使得代码的可复用性更高,也是模板方法模式的一个较好的示例。
然后我们在 LocalLoader 类使用 BitmapDecoder,代码如下: